use std::fs;
use std::io;

use std::io::prelude::*;

use crate::bytes_test::*;
use std::fs::OpenOptions;

#[test]
fn test() {
    let mut file = OpenOptions::new()
        .read(true)
        .write(true)
        .create(true)
        .open("foo.txt")
        .unwrap();

    let mut s = String::new();
    file.read_to_string(&mut s);
    println!("{}", s);
    let cur = file.seek(io::SeekFrom::Current(0)).unwrap();
    println!("Current cursor: {}", cur);
    let a = "\nsomething";
    file.write(a.as_bytes());
    let cur = file.seek(io::SeekFrom::Current(0)).unwrap();
    println!("Current cursor: {}", cur);
    file.flush();
}

fn write_test_data(file: &mut fs::File) {
    file.seek(io::SeekFrom::Start(0));
    let initial_offset = 4 + 60000 * (4 + 4 + 4);
    let mut data_offset = initial_offset;
    let mut data_body: Vec<u8> = Vec::new();
    let mut v = Vec::new();
    for i in 1..301 {
        let doc_data = format!("Hi, this is james #{:04} speaking", i);
        let bs = doc_data.as_bytes();

        let doc_info = DocInfo {
            id: i,
            start_offset: data_offset,
            length: bs.len() as u32,
        };
        v.push(doc_info);

        //println!("{:?}", doc_info);
        data_offset += bs.len() as u32;
        data_body.extend(bs);
    }
    let length = v.len() as u32;
    let bs = struct_to_bytes(&v[..]);
    let size_bs = length.to_be_bytes();
    file.write(&size_bs);
    file.write(&bs);
    file.seek(io::SeekFrom::Start(initial_offset as u64));
    file.write(&data_body);
    let end = file.seek(io::SeekFrom::End(0)).unwrap();
    println!("File length: {}", end);
}

fn read_doc(file: &mut fs::File, doc_id_list: &[u32]) {
    //let mut buf = vec![0u8; 4];
    //file.read_exact(&mut buf);
    let default_offset = 4 + 60000 * (4 + 4 + 4);
    file.seek(io::SeekFrom::Start(0));
    let mut piece = file.take(4);
    let mut buf = Vec::new();
    piece.read_to_end(&mut buf);
    let size = as_u32_be(&buf);
    let mut piece = file.take((size * (4 + 4 + 4)) as u64);
    let mut buf = Vec::new();
    piece.read_to_end(&mut buf);
    let doc_infos = struct_from_bytes(&buf);
    println!("header size: {}", size);

    println!("try to read doc: {:?}", doc_id_list);
    for doc_id in doc_id_list {
	let mut target_doc_info = DocInfo::default();

        for doc_info in &doc_infos {
            if doc_info.id == *doc_id {
                target_doc_info = *doc_info;
                break;
            }
        }

        if target_doc_info.id > 0 {
            file.seek(io::SeekFrom::Start(target_doc_info.start_offset as u64));
            let mut piece = file.take(target_doc_info.length as u64);
            let mut buf = Vec::new();
            piece.read_to_end(&mut buf);
            let s = String::from_utf8_lossy(&buf);
            println!("Found data: head = {:?}, body = {} ", target_doc_info, s);
        } else {
            println!("Cannot find data with id {}", doc_id);
        }
    }

    println!("Header doc id order:");
    for doc_info in doc_infos {
	print!("{} ", doc_info.id);
    }
    println!();
}

fn delete_doc(file: &mut fs::File, doc_id_list: &[u32]) {

    println!("try to delete doc: {:?}", doc_id_list);
    //let mut buf = vec![0u8; 4];
    //file.read_exact(&mut buf);
    let default_offset = 4 + 60000 * (4 + 4 + 4);
    file.seek(io::SeekFrom::Start(0));
    let mut piece = file.take(4);
    let mut buf = Vec::new();
    piece.read_to_end(&mut buf);
    let size = as_u32_be(&buf);
    let mut piece = file.take((size * (4 + 4 + 4)) as u64);
    let mut buf = Vec::new();
    piece.read_to_end(&mut buf);
    let mut doc_infos = struct_from_bytes(&buf);
    let mut target_doc_info = DocInfo::default();

    for doc_id in doc_id_list {
        let mut target_index = 0;
        for (pos, doc_info) in doc_infos.iter().enumerate() {
            if doc_info.id == *doc_id {
                target_doc_info = *doc_info;
                target_index = pos;
                break;
            }
        }

        if target_doc_info.id > 0 {
            let mut to_swap_index = 0;
            let mut farrest_offset = 0;

            for (pos, e) in doc_infos.iter().enumerate() {
                if e.start_offset > farrest_offset {
                    farrest_offset = e.start_offset;
                    to_swap_index = pos;
                }
            }
            let mut swap_doc = doc_infos[to_swap_index];
            let new_length = swap_doc.start_offset;

            let old_offset = swap_doc.start_offset;
            swap_doc.start_offset = target_doc_info.start_offset;
            doc_infos[to_swap_index] = swap_doc;

            let temp = doc_infos[doc_infos.len() - 1];
            doc_infos[target_index] = temp;
            doc_infos.pop();

            file.seek(io::SeekFrom::Start(old_offset as u64));
            let mut piece = file.take(swap_doc.length as u64);
            let mut buf = Vec::new();
            piece.read_to_end(&mut buf);

            file.seek(io::SeekFrom::Start(swap_doc.start_offset as u64));
            file.write(&buf);
            file.set_len(new_length as u64);

            println!("doc {} deleted, new file length: {}", target_doc_info.id, new_length);

            //let s = String::from_utf8_lossy(&buf);
            //println!("Found data: head = {:?}, body = {} ", target_doc_info, s);
        } else {
            println!("Cannot find data with id {}", doc_id);
        }
    }

    file.seek(io::SeekFrom::Start(0));
    let bs = struct_to_bytes(&doc_infos[..]);
    let size_bs = (doc_infos.len() as u32).to_be_bytes();
    file.write(&size_bs);
    file.write(&bs);
}

fn delete_doc_sorted(file: &mut fs::File, doc_id_list: &[u32]) {

    println!("try to delete doc: {:?}", doc_id_list);
    //let mut buf = vec![0u8; 4];
    //file.read_exact(&mut buf);
    let default_offset = 4 + 60000 * (4 + 4 + 4);
    file.seek(io::SeekFrom::Start(0));
    let mut piece = file.take(4);
    let mut buf = Vec::new();
    piece.read_to_end(&mut buf);
    let size = as_u32_be(&buf);
    let mut piece = file.take((size * (4 + 4 + 4)) as u64);
    let mut buf = Vec::new();
    piece.read_to_end(&mut buf);
    let mut doc_infos = struct_from_bytes(&buf);
    let mut target_doc_info = DocInfo::default();

    for doc_id in doc_id_list {
        let mut target_index = 0;
        for (pos, doc_info) in doc_infos.iter().enumerate() {
            if doc_info.id == *doc_id {
                target_doc_info = *doc_info;
                target_index = pos;
                break;
            }
        }

        if target_doc_info.id > 0 {
	    doc_infos.remove(target_index);
        } else {
            println!("Cannot find data with id {}", doc_id);
        }
    }

    file.seek(io::SeekFrom::Start(0));
    let bs = struct_to_bytes(&doc_infos[..]);
    let size_bs = (doc_infos.len() as u32).to_be_bytes();
    file.write(&size_bs);
    file.write(&bs);
}

#[test]
fn test_collection() {
    let file_name = "test_collection.minidb";
    let header_size: usize = 4 + 60000 * (4 + 4 + 4);
    {
        let mut file = OpenOptions::new()
            .read(true)
            .write(true)
            .create(true)
            .open(file_name)
            .unwrap();
        let end = file.seek(io::SeekFrom::End(0)).unwrap();
        if end < header_size as u64 {
            write_test_data(&mut file);
        }
    }

    // delete
    {
        let mut file = OpenOptions::new()
            .read(true)
            .write(true)
            .open(file_name)
            .unwrap();
        delete_doc_sorted(&mut file, &[44, 55]);
    }

    // read
    {
        let mut file = OpenOptions::new()
            .read(true)
            .write(true)
            .open(file_name)
            .unwrap();
        read_doc(&mut file, &[33, 44, 300]);
    }
}
